package com.ds.lens.data.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.Page;
import com.google.common.collect.Lists;
import com.ds.lens.data.common.asserts.Asserts;
import com.ds.lens.data.common.client.vo.LensApplicationNameOutVO;
import com.ds.lens.data.common.client.vo.LensApplicationOutVO;
import com.ds.lens.data.common.constant.Constants;
import com.ds.lens.data.common.util.HttpUtil;
import com.ds.lens.data.security.SecuritytConstants;
import com.ds.lens.data.service.dao.LensApplicationDao;
import com.ds.lens.data.service.dao.LensApplicationUserRelationDao;
import com.ds.lens.data.service.dao.LensRoleDao;
import com.ds.lens.data.service.dao.LensUserRoleRelationDao;
import com.ds.lens.data.service.dao.entity.LensApplication;
import com.ds.lens.data.service.dao.entity.LensApplicationUserRelation;
import com.ds.lens.data.service.dao.entity.LensRole;
import com.ds.lens.data.service.dao.entity.LensUserRoleRelation;
import com.ds.lens.data.vo.in.PagedConditionInVO;
import com.ds.lens.data.vo.in.User;
import com.ds.lens.data.vo.in.v2.application.LensApplicationInVO;
import com.ds.lens.data.vo.in.v2.application.LensApplicationSearchVO;
import com.ds.lens.data.vo.out.PageResult;
import com.ds.lens.data.vo.out.v2.application.ApplicationUserOutVO;
import com.ds.lens.data.vo.out.v2.application.LensApplicationVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.message.BasicHeader;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Description:
 *
 * @author WeiShaoying
 * @date 2019-11-05
 */
@Slf4j
@Service
public class LensApplicationService extends BaseService {

    @Value("${lens.baseinfo.host}")
    private String baseInfoHost;
    @Value("${lens.baseinfo.param.domain}")
    private String domain;
    @Value("${lens.baseinfo.param.token}")
    private String token;

    @Autowired
    LensApplicationDao lensApplicationDao;
    @Autowired
    LensApplicationUserRelationDao lensApplicationUserRelationDao;
    @Autowired
    LensUserRoleRelationDao lensUserRoleRelationDao;
    @Autowired
    LensRoleDao lensRoleDao;


    public LensApplicationOutVO findLensApplicationByKey(String appKey) {
        Asserts.assertNotEmpty(appKey, Constants.ErrorCode.PARAM_ERROR, "appKey can not be empty.");
        LensApplication lensApplication = lensApplicationDao.queryApplicationByKey(appKey);
        LensApplicationOutVO outVO = new LensApplicationOutVO();
        BeanUtils.copyProperties(lensApplication, outVO);
        outVO.setCreatedTime(lensApplication.getCreatedTime() != null ? lensApplication.getCreatedTime().getTime() : null);
        outVO.setModifiedTime(lensApplication.getModifiedTime() != null ? lensApplication.getModifiedTime().getTime() : null);
        return outVO;
    }

    public LensApplicationOutVO findLensApplicationById(Long appId) {
        Asserts.assertNotNull(appId, Constants.ErrorCode.PARAM_ERROR, "appId can not be empty.");
        LensApplication lensApplication = lensApplicationDao.selectByPrimaryKey(appId);
        LensApplicationOutVO outVO = new LensApplicationOutVO();
        BeanUtils.copyProperties(lensApplication, outVO);
        outVO.setCreatedTime(lensApplication.getCreatedTime() != null ? lensApplication.getCreatedTime().getTime() : null);
        outVO.setModifiedTime(lensApplication.getModifiedTime() != null ? lensApplication.getModifiedTime().getTime() : null);
        return outVO;
    }


    public void deleteAppUserByRelationId(long relationId, User user) {
        LensApplicationUserRelation lensApplicationUserRelation = lensApplicationUserRelationDao.selectByPrimaryKey(relationId);
        Asserts.assertNotNull(lensApplicationUserRelation, Constants.ErrorCode.DATA_NOT_EXIST);
        Asserts.assertTrue((user.isAdmin() || lensApplicationUserRelationDao.isApplicationAdmin(lensApplicationUserRelation.getApplicationId(), user.getUsername())), Constants.ErrorCode.NO_PERMISSION);
        Asserts.assertTrue(lensApplicationUserRelationDao.deleteByPrimaryKey(relationId) > 0, Constants.ErrorCode.SYSTEM_EXCEPTION);
    }

    /**
     * Query applications by userName
     *
     * @param appId
     * @return
     */
    public List<ApplicationUserOutVO> findAppUsersByAppId(long appId) {
        List<LensApplicationUserRelation> relations = lensApplicationUserRelationDao.queryAppUsersByAppId(appId);
        if (CollectionUtils.isEmpty(relations)) {
            return Collections.emptyList();
        }
        return relations.stream().map(relation -> {
            ApplicationUserOutVO outVO = new ApplicationUserOutVO();
            outVO.setId(relation.getId());
            outVO.setName(relation.getUserName());
            outVO.setRole(Constants.ApplicationRoleEnum.enumOf(relation.getApplicationRole()).getName());
            return outVO;
        }).collect(Collectors.toList());
    }

    /**
     * Save the relationship between users and applications
     *
     * @param applicationId
     * @param userName
     * @return
     */
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, rollbackFor = Throwable.class)
    public boolean saveApplicationUserRelation(long applicationId, String userName, int applicationRole, int userType, User user) {
        Asserts.assertNotEmpty(userName, Constants.ErrorCode.PARAM_ERROR, "user name can not be empty.");
        Asserts.assertNotNull(user, Constants.ErrorCode.PARAM_ERROR, "User can not be null.");
        Asserts.assertTrue((user.isAdmin() || lensApplicationUserRelationDao.isApplicationAdmin(applicationId, user.getUsername())), Constants.ErrorCode.NO_PERMISSION);

        LensApplicationUserRelation relation = new LensApplicationUserRelation();
        relation.setApplicationId(applicationId);
        relation.setUserName(userName);
        relation.setUserType((byte) userType);
        relation.setApplicationRole((byte) applicationRole);
        //校验该关系是否存在
        Asserts.assertFalse(lensApplicationUserRelationDao.associated(applicationId, userName), Constants.ErrorCode.PARAM_ERROR, "The relationship already exists.");
        LensRole role = lensRoleDao.queryRoleByName(Constants.RoleEnum.USER.getName());
        if (null != role && !lensUserRoleRelationDao.relationExisted(userName, role.getId())) {
            LensUserRoleRelation userRoleRelation = new LensUserRoleRelation();
            userRoleRelation.setRoleId(role.getId());
            userRoleRelation.setUserName(userName);
            Asserts.assertTrue(lensUserRoleRelationDao.insert(userRoleRelation) > 0, Constants.ErrorCode.SYSTEM_EXCEPTION);
        }

        return lensApplicationUserRelationDao.insert(relation) > 0;
    }

    /**
     * Query applications by userName
     *
     * @param userName
     * @return
     */
    public List<LensApplication> findApplicationByUserName(String userName) {
        Asserts.assertNotEmpty(userName, Constants.ErrorCode.PARAM_ERROR, "User name can not be empty.");
        return lensApplicationDao.queryLensApplicationsByUserName(userName);
    }

    /**
     * Query userNames by applicationId
     *
     * @param applicationId
     * @return
     */
    public List<String> findUserNamesByApplicationId(Long applicationId) {
        Asserts.assertNotNull(applicationId, Constants.ErrorCode.PARAM_ERROR, "Application id can not be null.");
        return lensApplicationUserRelationDao.queryUserNamesByApplicationId(applicationId);
    }

    /**
     * Does the relationship exist
     *
     * @param applicationId
     * @param userName
     * @return boolean
     */
    public boolean associated(Long applicationId, String userName) {
        Asserts.assertNotNull(applicationId, Constants.ErrorCode.PARAM_ERROR, "Application id can not be null.");
        Asserts.assertNotEmpty(userName, Constants.ErrorCode.PARAM_ERROR, "User name can not be empty.");
        return lensApplicationUserRelationDao.associated(applicationId, userName);
    }

    /**
     * Query SSO Users from base info API
     *
     * @param param
     * @return
     */
    public List<User> findBaseInfoUsers(String param) {
        if (StringUtils.isNotEmpty(param)) {
            String url = baseInfoHost + API_BASE_INFO.replace("$PARAM", param);
            Header header = new BasicHeader(SecuritytConstants.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString((domain + ":" + token).getBytes()));
            ResponseEntity entity = HttpUtil.get(url, null, header, HTTP_TIMEOUT);
            if (null == entity || entity.getStatusCode() != HttpStatus.OK) {
                log.error("Call base info rest API error. Return information：" + JSONObject.toJSONString(entity));
                return Collections.emptyList();
            }
            JSONObject body = (JSONObject) entity.getBody();
            Asserts.assertNotNull(body, Constants.ErrorCode.PARAM_ERROR, "Response body can not be null.");
            Integer total = body.getInteger("total");
            if (total > 0) {
                JSONArray apps = body.getJSONArray("users");
                if (CollectionUtils.isNotEmpty(apps)) {
                    User user;
                    JSONObject jsonObject;
                    List<User> users = Lists.newArrayListWithCapacity(apps.size());
                    for (int i = 0; i < apps.size(); i++) {
                        user = new User();
                        jsonObject = apps.getJSONObject(i);
                        user.setUsername(jsonObject.getString("username"));
                        user.setEmail(jsonObject.getString("email"));
                        user.setUid(jsonObject.getString("uid"));
                        user.setName(jsonObject.getString("name"));
                        user.setTitle(jsonObject.getString("title"));
                        users.add(user);
                    }
                    return users;
                }
            }
        }
        return Collections.emptyList();
    }

    /**
     * Save application
     *
     * @param lensApplicationInVO
     * @param user
     * @return
     */
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, rollbackFor = Throwable.class)
    public boolean saveLensApplication(LensApplicationInVO lensApplicationInVO, User user) {
        Asserts.assertNotNull(user, Constants.ErrorCode.PARAM_ERROR, "User can not be null.");
        Asserts.assertNotNull(lensApplicationInVO, Constants.ErrorCode.PARAM_ERROR, "Parameter can not be null.");
        Asserts.assertNotEmpty(lensApplicationInVO.getName(), Constants.ErrorCode.PARAM_ERROR, "Application name can not be empty.");
        Asserts.assertFalse(lensApplicationDao.usedAppName(lensApplicationInVO.getName()), Constants.ErrorCode.PARAM_ERROR, "Duplicate application name.");
        String appKey = getAppKey();
        LensApplication lensApplication = new LensApplication();
        lensApplication.setAppKey(appKey);
        lensApplication.setName(lensApplicationInVO.getName());
        Date date = new Date();
        lensApplication.setCreatedTime(date);
        lensApplication.setModifiedTime(date);
        lensApplication.setCreator(user.getUsername());
        lensApplication.setMender(user.getUsername());
        return lensApplicationDao.insert(lensApplication) > 0;
    }

    private String getAppKey() {
        String key = UUID.randomUUID().toString();
        if (lensApplicationDao.usedAppKey(key)) {
            getAppKey();
        }
        return key;
    }

    /**
     * Paging query applications
     *
     * @param searchVO
     * @return
     */
    public PageResult<LensApplicationVO> findLensApplicationByPage(PagedConditionInVO<LensApplicationSearchVO> searchVO) {

        Page<LensApplication> page = lensApplicationDao.queryLensApplications(getPageCondition(searchVO));
        if (CollectionUtils.isEmpty(page.getResult())) {
            return PageResult.of(Collections.emptyList(), 0L, page.getPageNum(), page.getPageSize());
        }
        List<LensApplicationVO> result = getLensApplicationVOS(page);

        return PageResult.of(result, page.getTotal(), page.getPageNum(), page.getPageSize());
    }

    private List<LensApplicationVO> getLensApplicationVOS(Page<LensApplication> page) {
        List<LensApplicationVO> result = Lists.newArrayListWithCapacity(page.getResult().size());
        page.getResult().forEach(lensApplication -> {
            LensApplicationVO lensApplicationVO = new LensApplicationVO();
            BeanUtils.copyProperties(lensApplication, lensApplicationVO);
            result.add(lensApplicationVO);
        });
        return result;
    }

    public List<LensApplicationNameOutVO> listAllApplication() {
        List<LensApplication> list = lensApplicationDao.listAllApplications();
        return getApplicationNameList(list);
    }

    public List<LensApplicationNameOutVO> listUpdatedApplication(Long lastModifyTime) {
        List<LensApplication> list = lensApplicationDao.listUpdatedApplications(new Date(lastModifyTime));
        return getApplicationNameList(list);
    }

    private List<LensApplicationNameOutVO> getApplicationNameList(List<LensApplication> list) {
        List<LensApplicationNameOutVO> outList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(list)) {
            for (LensApplication application : list) {
                LensApplicationNameOutVO appName = new LensApplicationNameOutVO();
                BeanUtils.copyProperties(application, appName);
                if (application.getModifiedTime() != null) {
                    appName.setModifiedTime(application.getModifiedTime().getTime());
                }
                outList.add(appName);
            }
        }
        return outList;
    }
}
